package com.itheima.leetcode.od.b.graph;

import java.util.*;
import java.util.stream.Collectors;

/**
 * (C卷,200分)- 特殊的加密算法（Java & JS & Python & C）
 * <p>
 * 题目描述
 * <p>
 * 有一种特殊的加密算法，明文为一段数字串，经过密码本查找转换，生成另一段密文数字串。
 * <p>
 * 规则如下：
 * <p>
 * 明文为一段数字串由 0~9 组成
 * 密码本为数字 0~9 组成的二维数组
 * 需要按明文串的数字顺序在密码本里找到同样的数字串，密码本里的数字串是由相邻的单元格数字组成，上下和左右是相邻的，注意：对角线不相邻，同一个单元格的数字不能重复使用。
 * 每一位明文对应密文即为密码本中找到的单元格所在的行和列序号（序号从0开始）组成的两个数宇。
 * <p>
 * 如明文第 i 位 Data[i] 对应密码本单元格为 Book[x][y]，则明文第 i 位对应的密文为X Y，X和Y之间用空格隔开。
 * 如果有多条密文，返回字符序最小的密文。
 * <p>
 * 如果密码本无法匹配，返回"error"。
 * <p>
 * 请你设计这个加密程序。
 * <p>
 * 示例1：
 * <p>
 * 密码本：
 * <p>
 * 0 0 2
 * <p>
 * 1 3 4
 * <p>
 * 6 6 4
 * <p>
 * 明文："3"，密文："1 1"
 * <p>
 * 示例2：
 * <p>
 * 密码本：
 * <p>
 * 0 0 2
 * <p>
 * 1 3 4
 * <p>
 * 6 6 4
 * <p>
 * 明文："0 3"，密文："0 1 1 1"
 * <p>
 * 示例3：
 * <p>
 * 密码本：
 * <p>
 * 0 0 2 4
 * <p>
 * 1 3 4 6
 * <p>
 * 3 4 1 5
 * <p>
 * 6 6 6 5
 * <p>
 * 明文："0 0 2 4"，密文："0 0 0 1 0 2 0 3" 和 "0 0 0 1 0 2 1 2"，返回字典序最小的"0 0 0 1 0 2 0 3"
 * <p>
 * 明文："8 2 2 3"，密文："error"，密码本中无法匹配
 * <p>
 * 输入描述
 * <p>
 * 第一行输入 1 个正整数 N，代表明文的长度（1 ≤ N ≤ 200）
 * <p>
 * 第二行输入 N 个明文组成的序列 Data[i]（0 ≤ Data[i] ≤ 9）
 * <p>
 * 第三行输入 1 个正整数 M，代表密文的长度
 * <p>
 * 接下来 M 行，每行 M 个数，代表密文矩阵
 * <p>
 * 输出描述
 * <p>
 * 输出字典序最小密文，如果无法匹配，输出"error"
 * <p>
 * 用例
 * <p>
 * 输入	2
 * <p>
 * 0 3
 * <p>
 * 3
 * <p>
 * 0 0 2
 * <p>
 * 1 3 4
 * <p>
 * 6 6 4
 * <p>
 * 输出	0 1 1 1
 * 说明	无
 * <p>
 * 输入	2
 * <p>
 * 0 5
 * <p>
 * 3
 * <p>
 * 0 0 2
 * <p>
 * 1 3 4
 * <p>
 * 6 6 4
 * <p>
 * 输出	error
 * 说明	找不到 0 5 的序列，返回error
 */
public class DFSSpecialEncryption {
    // 明文数字个数
    public static void main(String[] args) {
        /*Scanner sc = new Scanner(System.in);

        n = sc.nextInt();

        datas = new int[n];
        for (int i = 0; i < n; i++) {
            datas[i] = sc.nextInt();
        }

        // 记录密码本中元素值等于“明文第一个数字”的所有元素的位置
        ArrayList<int[]> starts = new ArrayList<>();

        m = sc.nextInt();

        secrets = new int[m][m];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < m; j++) {
                secrets[i][j] = sc.nextInt();

                // 如果密码本(i,j)位置元素指等于明文第一个数字值，则记录(i,j)作为一个出发位置
                if (datas[0] == secrets[i][j]) {
                    starts.add(new int[]{i, j});
                }
            }
        }*/

        int n = 2;

        int[] datas = Arrays.stream("0 3".split(" ")).mapToInt(Integer::parseInt).toArray();

        int m = 3;

        int[][] secrets = Arrays.stream("0 0 2\n1 3 4\n6 6 4".split("\n"))
                .map(arr -> Arrays.stream(arr.split(" "))
                        .mapToInt(Integer::parseInt)
                        .toArray())
                .toArray(int[][]::new);

        // 记录密码本中元素值等于“明文第一个数字”的所有元素的位置
        ArrayList<int[]> starts = new ArrayList<>();

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < m; j++) {
                // 如果密码本(i,j)位置元素指等于明文第一个数字值，则记录(i,j)作为一个出发位置
                if (datas[0] == secrets[i][j]) {
                    starts.add(new int[]{i, j}); // starts按坐标升序排列
                }
            }
        }

        System.out.println(getResult(starts, secrets, datas, n));
    }

    // 上，左，右，下偏移量，注意这里的顺序是有影响的，即下一步偏移后产生的密文的字符序必然是：上 < 左 < 右 < 下
    static int[][] offsets = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};

    public static String getResult(ArrayList<int[]> starts, int[][] secrets, int[] datas, int n) {
        int m = secrets.length;
        for (int[] start : starts) {
            // 出发位置
            int x = start[0];
            int y = start[1];

            // used[i][j]用于记录密码本(i,j)元素是否已使用
            boolean[][] used = new boolean[m][m];
            // 出发点位置元素已使用
            used[x][y] = true;

            // 记录结果路径各节点位置
            LinkedList<int[]> path = new LinkedList<>();
            // 出发点位置记录
            path.add(new int[]{x, y});

            // 开始深搜
            if (dfs(x, y, 1, path, used, secrets, datas, n)) { // 第一个满足条件的元素组成的密文绝对比后面元素组成的密文小
                return path.stream()
                        .map(arr -> Arrays.stream(arr)
                                .boxed()
                                .map(String::valueOf)
                                .collect(Collectors.joining(" ")))
                        .collect(Collectors.joining(" "));
            }
        }

        return "error";
    }

    /**
     * @param x       当前位置横坐标
     * @param y       当前位置纵坐标
     * @param index   datas[index]是将要匹配的明文数字
     * @param path    路径
     * @param used    密码本各元素使用情况
     * @param secrets 密码本
     * @param datas   明文
     * @param n       明文数字个数
     * @return 是否找到符合要求的路径
     */
    public static boolean dfs(int x, int y, int index, LinkedList<int[]> path, boolean[][] used, int[][] secrets, int[] datas, int n) {
        int m = secrets.length;

        // 已找到明文最后一个数字，则找到符合要求的路径
        if (index == n) {
            return true;
        }

        // 否则，进行上、左、右、下四个方向偏移，注意这里的顺序是有影响的，即下一步偏移后产生的密文的字符序必然是：上 < 左 < 右 < 下
        for (int[] offset : offsets) {
            // 新位置
            int newX = x + offset[0];
            int newY = y + offset[1];

            // 新位置越界，或者新位置已使用，或者新位置不是目标值，则跳过
            if (newX < 0 || newX >= m || newY < 0 || newY >= m
                    || used[newX][newY]
                    || secrets[newX][newY] != datas[index]) {
                continue;
            }

            // 递归进入新位置
            path.add(new int[]{newX, newY});
            used[newX][newY] = true;

            // 如果当前分支可以找到符合要求的路径，则返回
            if (dfs(newX, newY, index + 1, path, used, secrets, datas, n)) {
                return true;
            }

            // 否则，回溯
            used[newX][newY] = false;
            path.pop();
        }

        return false;
    }
}